#![allow(non_snake_case)]

use regex::Regex;
use std::fs::File;
use std::io::{BufRead, BufReader, Seek, SeekFrom};
use std::cmp::Ordering;
use std::path::Path;

enum Rule {
    GB11643_1999,   // 国家标准，18位
    GB11643_1989    // 国家标准，15位
}

fn check_rule(id_card_no: &str) -> Option<Rule> {
    if id_card_no.len() == 18 {
        Some(Rule::GB11643_1999)
    } else if id_card_no.len() == 15 {
        Some(Rule::GB11643_1989)
    } else {
        None
    }
}

const WRONG_MESSAGE: &str = "身份证号码错误";
const RIGHT_MESSAGE: &str = "身份证号码正确";
const MAN: &str = "男";
const WOMAN: &str = "女";

fn check_region_code(region_code: &str) -> Option<String> {
    // 检查地区编码是否有效
    let project_root = env!("CARGO_MANIFEST_DIR");
    let file_path = Path::new(project_root).join("region.tsv");
    let region_file = File::open(file_path).unwrap();
    let mut reader = BufReader::new(region_file);
    let file_len = reader.get_ref().metadata().unwrap().len();
    let province_code = region_code[..2].to_string() + "0000";
    let city_code = region_code[..4].to_string() + "00";
    let district_code = region_code.to_string();
    let code_group = [province_code, city_code, district_code];
    let mut name_group: [String; 3] = [String::new(), String::new(), String::new()];
    for (i, target_code) in code_group.iter().enumerate() {
        let mut start = 0;
        let mut end = file_len;
        let mut successed = false;
        while start < end {
            let mid = (start + end) / 2;
            reader.seek(SeekFrom::Start(mid)).unwrap();
            let mut line = String::new();
            if mid > 0 {
                let _ = reader.read_line(&mut line);
                line.clear();
            }
            if reader.read_line(&mut line).unwrap() == 0 {
                break;
            }
            let parts: Vec<&str> = line.split('\t').collect();
            if parts.len() < 3 {
                continue;
            }
            let code = parts[1];
            let name = parts[2].trim();
            match code.cmp(target_code) {
                Ordering::Less => start = mid + 1,
                Ordering::Greater => end = mid,
                Ordering::Equal => {
                    name_group[i] = name.to_string();
                    successed = true;
                    break;
                }
            }
        }
        if !successed {
            return None;
        }
    }
    Some(format!("{}-{}-{}", name_group[0], name_group[1], name_group[2]))
}

fn check_birth_date(birth_year: &str, birth_month: &str, birth_day: &str) -> Option<String> {
    // 检查生日日期是否有效
    let is_leap_year = |year: i32|{
        (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
    };
    if let (Ok(year), Ok(month), Ok(day)) = (birth_year.parse::<i32>(), birth_month.parse::<u32>(), birth_day.parse::<u32>()) {
        if month > 12 || month < 1 {
            return None
        }
        let month_days = match month {
            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
            4 | 6 | 9 | 11 => 30,
            2 => if is_leap_year(year) { 29 } else { 28 },
            _ => return None
        };
        if day > month_days || day < 1 {
            return None
        }
        Some(format!("{:04}年{:02}月{:02}日", year, month, day))
    } else {
        None
    }
}

fn check_sequence_code(sequence_code: &str) -> Option<String> {
    // 检查性别
    if let Ok(code) = sequence_code.parse::<u32>() {
        if code % 2 == 1 {
            Some(MAN.to_string())
        } else {
            Some(WOMAN.to_string())
        }
    } else {
        None
    }
}

fn check_check_code(id_card_no: &str, check_code: &str) -> bool {
    // 检查校验码
    const WEIGHTS: [i32; 17] = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
    let mut sum = 0;
    for (i, digit) in id_card_no.chars().enumerate() {
        if i == 17 {
            break;
        }
        if digit.is_numeric() {
            sum += WEIGHTS[i] * digit.to_digit(10).unwrap() as i32;
        }
    }
    let check_digit = (12 - sum % 11) % 11;
    let right_check_code = if check_digit == 10 { "X".to_string() } else { check_digit.to_string() };
    right_check_code == check_code
}

fn check_id_card_GB11643_1999(id_card_no: &str) -> String {
    let re = Regex::new(r"^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([\dX])$").unwrap();
    if let Some(caps) = re.captures(id_card_no) {
        let region_code = &caps.get(1).unwrap().as_str().to_string();
        let birth_year = &caps.get(2).unwrap().as_str().to_string();
        let birth_month = &caps.get(3).unwrap().as_str().to_string();
        let birth_day = &caps.get(4).unwrap().as_str().to_string();
        let sequence_code = &caps.get(5).unwrap().as_str().to_string();
        let check_code = &caps.get(6).unwrap().as_str().to_string();
        match (check_region_code(region_code), check_birth_date(birth_year, birth_month, birth_day), check_sequence_code(sequence_code), check_check_code(id_card_no, check_code)) {
            (Some(region), Some(birth_date), Some(gender), true) => {
                format!("{},{},{},{}", RIGHT_MESSAGE.to_string(), gender, birth_date, region)
            }
            _ => {
                WRONG_MESSAGE.to_string()
            }
        }
    } else {
        WRONG_MESSAGE.to_string()
    }
}

fn check_id_card_GB11643_1989(id_card_no: &str) -> String {
    let re = Regex::new(r"^(\d{6})(\d{2})(\d{2})(\d{2})(\d{3})$").unwrap();
    if let Some(caps) = re.captures(id_card_no) {
        let region_code = &caps.get(1).unwrap().as_str().to_string();
        let short_birth_year = &caps.get(2).unwrap().as_str().to_string();
        let birth_month = &caps.get(3).unwrap().as_str().to_string();
        let birth_day = &caps.get(4).unwrap().as_str().to_string();
        let sequence_code = &caps.get(5).unwrap().as_str().to_string();
        match (check_region_code(region_code), check_birth_date(&format!("19{}", short_birth_year), birth_month, birth_day), check_sequence_code(sequence_code)) {
            (Some(region), Some(birth_date), Some(gender)) => {
                format!("{},{},{},{}", RIGHT_MESSAGE.to_string(), gender, birth_date, region)
            }
            _ => {
                WRONG_MESSAGE.to_string()
            }
        }
    } else {
        WRONG_MESSAGE.to_string()
    }
}

pub fn check_id_card(id_card_no: &str) -> String {
    if let Some(rule) = check_rule(id_card_no) {
        match rule {
            Rule::GB11643_1999 => {
                check_id_card_GB11643_1999(id_card_no)
            }   
            Rule::GB11643_1989 => {
                check_id_card_GB11643_1989(id_card_no)
            }
        }
    } else {
        WRONG_MESSAGE.to_string()
    }
}
